Conversation
The crate used to be its own workspace rooted at runtime/, so cargo only worked from inside that directory: `cargo test --workspace` at the repo root had no manifest to find. Moving the workspace manifest (and the lock) to the root makes the whole-repo commands behave the way every other Rust repo's do, while `cd runtime && cargo …` keeps working through the member. Dockerfile and Dockerfile.nightly pick up the new manifest/lock layout; dependabot and the CI cache now look at the root workspace. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
openabdev#39) OPENAB_TOOLS_MCP_URL put the loopback endpoint in the child's environment, but wiring it into the CLI was manual: `mcp add` per session plus a hand-edited allowlist, and both hard-coded a URL whose key dies on the next spawn. At spawn the runtime now writes the session-independent form for the CLI variants it knows — kiro-cli first: - ~/.kiro/settings/mcp.json gains a `computer` server that runs a small stdio bridge on the installer's bundled bun (or node). The bridge reads the URL from *its* environment — `${OPENAB_TOOLS_MCP_URL}` in `env`, the one place kiro expands variables — so the shared workspace file serves every session and never holds a key. A hard-coded per-session URL was the live failure in the field report: three sessions, one computer. - every ~/.kiro/agents/*.json gains `@computer/*` in allowedTools — the sandbox-served set under the platform-neutral alias — plus `@computer` in a restrictive `tools` list, and an agent that opted out of mcp.json gets its own copy of the server entry. Agent files are merged in place, never created. - nothing is written for a CLI the runtime does not know, when the tools plane is off, or when no JS runtime exists; the §9.3 env-var manual path stands in all three, and a failed merge never costs a spawn. Refs openabdev#39 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This was referenced Sep 30, 2026
Collaborator
|
Thank you @Reese-max — this PR is what made the shape of #39 clear, and its merge rules carry over directly. We're closing it in favour of #49, which keeps your approach to the config merge but changes where it lives and what it writes. You're credited as co-author on #49's commit ( Why a new PR instead of review rounds:
What carried over from your work: preserving other servers/settings, leaving malformed or wrong-shaped files byte-identical, never creating agent files, leaving dangling symlinks alone, atomic writes that keep the mode, and the |
thepagent
pushed a commit
that referenced
this pull request
Oct 1, 2026
…ok (#49) * feat(tools): wire the tools MCP into the CLI from an image startup hook (#39) The runtime keeps knowing no coding CLI's config format. Instead it gains one generic seam, `--startup-hook <FILE>`: an image-provided executable it runs once, after seeding and before serving, best-effort (failure, non-zero exit or a 30 s timeout is logged and serving continues). After seeding matters: a seed archive may carry the very config file the hook edits. The images ship deploy/hooks/startup-hook.sh (bash + jq; jq added to both Dockerfiles). For kiro-cli, when PTY_TOOLS_LISTEN is set, it writes #44's session-independent form into ~/.kiro/settings/mcp.json: computer = { url: http://<PTY_TOOLS_LISTEN>/mcp, headers: { Authorization: "Bearer ${OPENAB_TOOLS_MCP_TOKEN}" } } and adds @computer/* to allowedTools in existing agent files. No session name or key is ever on disk, so one shared workspace config serves every session and survives every key rotation. With the tools plane off, only the entry the hook wrote is withdrawn. A new CLI is a function in the hook, not a runtime change. Merge rules come from #45: other servers/settings preserved, wrong-shaped or malformed files left byte-identical, agent files never created, dangling symlinks left alone, atomic writes that keep the file's mode. Changed from #45: no stdio JS bridge (superseded by #44's header route), restrictive `tools` lists and `mcpServers` in agent files are not touched, key order is kept, and the knowledge lives in the image rather than the runtime. Closes #39. Supersedes #45. Co-authored-by: Reese-max <198568376+Reese-max@users.noreply.github.com> * fix(hook): scrub the hook's env, kill and reap its group, respect user entries Review round 1 on #49. - B1: the hook ran with the runtime's full environment while jq loaded ~/.jq from the shared workspace, so a planted ~/.jq could write e.g. an ECS credentials URI into a config the next session reads. The hook now gets env_clear() + the session allowlist + OPENAB_PTY_TOOLS_LISTEN, and the script runs jq with HOME=/nonexistent. - S1: the hook leads its own process group; on exit or timeout the group is SIGKILLed and reparented members are reaped (waitpid(-pgid)), so no orphan or zombie outlives it. - S2: main.rs documents why the exec'd, dumpable hook is safe (scrubbed env, no session yet). Inline step numbers fixed. - S3: the hook runs after the binds and is handed the tools address the runtime actually bound (same as OPENAB_TOOLS_MCP_ENDPOINT), not PTY_TOOLS_LISTEN re-read from env. IPv6 tested. - S4: tools-on replaces `computer` only when absent, ours (by header), or a hand-wired URL on this listener; anything else is left with a warning. - S5: a failed temp-file write is never renamed over the config. - Nits: non-regular paths and multi-document files left alone; HOME unset exits 0; "*" in allowedTools counts as trusted; WaitFailed outcome; jq version noted in the Dockerfile; `jq --version` in CI; tests for all of the above (39 shell cases, 8 hook tests incl. children and background members). Test-only ETXTBSY retry for scripts exec'd right after writing. * fix(hook): never fatal on a JoinError, tolerate non-UTF-8 env at boot Review round 2 nits on #49. - A failed spawn_blocking task (panic inside it) was propagated with `?`, contradicting the hook's best-effort contract; it is now a warning. - std::env::vars() panics on a non-UTF-8 variable, which with --startup-hook would have aborted the runtime at boot. Use vars_os() and drop variables that are not UTF-8 (the allowlist is ASCII anyway). Verified: runtime started with BAD=\xff\xfe in its env and a hook, hook ran and saw OPENAB_PTY_TOOLS_LISTEN, runtime kept serving. --------- Co-authored-by: Reese-max <198568376+Reese-max@users.noreply.github.com>
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #39
Summary
OPENAB_TOOLS_MCP_URLput the loopback endpoint in the child's environment,but wiring it into the CLI was manual:
mcp addper session plus ahand-edited allowlist, and both hard-coded a URL whose key dies on the next
spawn. At spawn the runtime now writes the session-independent form for
the CLI variants it knows — kiro-cli first:
~/.local/share/openab-pty/computer-mcp-bridge.js— a small stdio bridgeembedded in the binary and rewritten each spawn; every stdin line is one
JSON-RPC message POSTed to
$OPENAB_TOOLS_MCP_URL(JSON and SSE replies,Mcp-Session-Idechoed, per-id JSON-RPC errors on HTTP failure).~/.kiro/settings/mcp.jsongains exactly thecomputerserver{command: <bundled bun / PATH bun / node>=18, args: [bridge], env: {OPENAB_TOOLS_MCP_URL: "${OPENAB_TOOLS_MCP_URL}"}}— every otherserver and setting is preserved, and the file never holds a URL or key, so
the shared workspace config needs no refresh when the key rotates.
~/.kiro/agents/*.jsongains@computer/*inallowedTools, plus@computerin a restrictivetoolslist and acomputerentry inmcpServersfor agents that opt out of the shared mcp.json. Agent filesare merged, never created.
symlinks, preserve existing file modes). Malformed, non-object, or
foreign-shaped files are left byte-identical; dangling symlinks are left
alone rather than replaced.
stands.
tools_listenunset → injection never runs. A failed merge warnsand never costs a spawn.
Detection:
kiro-clion the child's PATH or under the installer's~/.locallayout; JS runtime = bundled bun →~/.local/share/kiro-cli/bun→ PATH bun → PATH node (with a
--versionprobe, node ≥ 18 required forfetch).Also includes a preparatory commit hoisting the crate into a repo-root
Cargo workspace (
Cargo.toml/Cargo.lockat root), socargo test/fmt/clippy --workspacework from a checkout root as well as fromruntime/; Dockerfiles, CI cache, and dependabot updated to match.Test plan
cargo fmt --all -- --checkcargo test --workspace(175 unit + 13 config tests incl. injectionmerge/preservation, unknown-variant, tools-off, node<18, dangling
symlink, mode-preservation, and restart-rotation byte-identical)
cargo clippy --workspace --all-targets -- -D warningsround-trip, SSE multi-line normalization, notification silence
rotates the env key while the file stays byte-identical; unwritable
workspace still spawns
Docs:
runtime/CLIENT-CONTRACT.md§9.3 rewritten for the auto-injection +manual fallback;
docs/k8s-howto.md§6 notes the zero-step CLI side.Generated with Devin